﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Security.Cryptography;

using LongNet.Util;

namespace LongNet.Security
{
    /// <summary>
    /// This class provides common cryptography services.
    /// This class cannot be inherited.
    /// </summary>
    public static partial class CryptoService
    {
        #region Simple Hash

        #region CryptoAlgorithm

        /// <summary>
        /// Crypto algorithms for <see cref="CryptoService"/>.
        /// </summary>
        public enum CryptoAlgorithm
        {
            /// <summary>
            /// Use MD5 hash algorithm.
            /// </summary>
            MD5,

            /// <summary>
            /// Use SHA-1 hash algorithm.
            /// </summary>
            SHA1,

            /// <summary>
            /// Use SHA-256 hash algorithm.
            /// </summary>
            SHA256,

            /// <summary>
            /// Use SHA-384 hash algorithm.
            /// </summary>
            SHA384,

            /// <summary>
            /// Use SHA-512 hash algorithm.
            /// </summary>
            SHA512
        }

        private static CryptoAlgorithm GetCryptoAlgorithmByName(string cryptoAlgorithm)
        {
            CryptoAlgorithm algorithm;
            switch (cryptoAlgorithm.ToUpper().Replace("-", ""))
            {
                case "SHA1":
                    algorithm = CryptoAlgorithm.SHA1;
                    break;
                case "SHA256":
                    algorithm = CryptoAlgorithm.SHA256;
                    break;
                case "SHA384":
                    algorithm = CryptoAlgorithm.SHA384;
                    break;
                case "SHA512":
                    algorithm = CryptoAlgorithm.SHA512;
                    break;
                default:
                    algorithm = CryptoAlgorithm.MD5;
                    break;
            }
            return algorithm;
        }

        #endregion

        #region ComputeHash/VerifyHash

        /// <summary>
        /// Generates a hash for the given plain text value.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <returns>The computed hash code.</returns>
        public static byte[] ComputeHash(string plainText, string cryptoAlgorithm)
        {
            return ComputeHash(plainText, GetCryptoAlgorithmByName(cryptoAlgorithm));
        }

        /// <summary>
        /// Generates a hash for the given plain text value and returns hexadecimal result.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <returns>Hash value formatted as a hexadecimal string.</returns>
        public static string ComputeHashToHexString(string plainText, string cryptoAlgorithm)
        {
            return Utils.ByteArrayToHexString(ComputeHash(plainText, cryptoAlgorithm));
        }

        /// <summary>
        /// Generates a hash for the given plain text value and returns a
        /// base64-encoded result.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <returns>Hash value formatted as a base64-encoded string.</returns>
        public static string ComputeHashToBase64String(string plainText, string cryptoAlgorithm)
        {
            return Convert.ToBase64String(ComputeHash(plainText, cryptoAlgorithm));
        }

        /// <summary>
        /// Generates a hash for the given plain text value.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <returns>The computed hash code.</returns>
        public static byte[] ComputeHash(string plainText, CryptoAlgorithm cryptoAlgorithm)
        {
            // Convert plain text into a byte array.
            byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);

            HashAlgorithm hash;
            // Initialize appropriate hashing algorithm class.
            switch (cryptoAlgorithm)
            {
                case CryptoAlgorithm.SHA1:
                    hash = new SHA1Managed();
                    break;
                case CryptoAlgorithm.SHA256:
                    hash = new SHA256Managed();
                    break;
                case CryptoAlgorithm.SHA384:
                    hash = new SHA384Managed();
                    break;
                case CryptoAlgorithm.SHA512:
                    hash = new SHA512Managed();
                    break;
                default:
                    hash = new MD5CryptoServiceProvider();
                    break;
            }

            // Compute hash value of our plain text with appended salt.
            byte[] hashBytes = hash.ComputeHash(plainTextBytes);

            return hashBytes;
        }

        /// <summary>
        /// Generates a hash for the given plain text value and returns hexadecimal result.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <returns>Hash value formatted as a hexadecimal string.</returns>
        public static string ComputeHashToHexString(string plainText, CryptoAlgorithm cryptoAlgorithm)
        {
            return Utils.ByteArrayToHexString(ComputeHash(plainText, cryptoAlgorithm));
        }


        /// <summary>
        /// Generates a hash for the given plain text value and returns a
        /// base64-encoded result.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <returns>Hash value formatted as a base64-encoded string.</returns>
        public static string ComputeHashToBase64String(string plainText, CryptoAlgorithm cryptoAlgorithm)
        {
            return Convert.ToBase64String(ComputeHash(plainText, cryptoAlgorithm));
        }

        /// <summary>
        /// Compares a hash of the specified plain text value to a given hash
        /// value.
        /// </summary>
        /// <param name="plainText">
        /// Plain text to be verified against the specified hash. The function
        /// does not check whether this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", 
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified,
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="hashedValue">
        /// Hash value produced by ComputeHashToHexString function.
        /// </param>
        /// <returns>
        /// If computed hash mathes the specified hash the function the return
        /// value is true; otherwise, the function returns false.
        /// </returns>
        public static bool VerifyHashInHexString(string plainText, string cryptoAlgorithm, string hashedValue)
        {
            return VerifyHashInHexString(plainText, GetCryptoAlgorithmByName(cryptoAlgorithm), hashedValue);
        }

        /// <summary>
        /// Compares a hash of the specified plain text value to a given hash
        /// value.
        /// </summary>
        /// <param name="plainText">
        /// Plain text to be verified against the specified hash. The function
        /// does not check whether this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", 
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified,
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="hashedValue">
        /// Hash value produced by ComputeHashToHexString function.
        /// </param>
        /// <returns>
        /// If computed hash mathes the specified hash the function the return
        /// value is true; otherwise, the function returns false.
        /// </returns>
        public static bool VerifyHashInHexString(string plainText, CryptoAlgorithm cryptoAlgorithm, string hashedValue)
        {
            // Compute a new hash string.
            string expectedHashString = ComputeHashToHexString(plainText, cryptoAlgorithm);

            // If the computed hash matches the specified hash,
            // the plain text value must be correct.
            return (hashedValue == expectedHashString);
        }

        /// <summary>
        /// Compares a hash of the specified plain text value to a given hash
        /// value.
        /// </summary>
        /// <param name="plainText">
        /// Plain text to be verified against the specified hash. The function
        /// does not check whether this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", 
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified,
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="hashedValue">
        /// Hash value produced by ComputeHashToBase64String function.
        /// </param>
        /// <returns>
        /// If computed hash mathes the specified hash the function the return
        /// value is true; otherwise, the function returns false.
        /// </returns>
        public static bool VerifyHashInBase64String(string plainText, string cryptoAlgorithm, string hashedValue)
        {
            return VerifyHashInBase64String(plainText, GetCryptoAlgorithmByName(cryptoAlgorithm), hashedValue);
        }

        /// <summary>
        /// Compares a hash of the specified plain text value to a given hash
        /// value.
        /// </summary>
        /// <param name="plainText">
        /// Plain text to be verified against the specified hash. The function
        /// does not check whether this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", 
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified,
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="hashedValue">
        /// Hash value produced by ComputeHashToBase64String function.
        /// </param>
        /// <returns>
        /// If computed hash mathes the specified hash the function the return
        /// value is true; otherwise, the function returns false.
        /// </returns>
        public static bool VerifyHashInBase64String(string plainText, CryptoAlgorithm cryptoAlgorithm, string hashedValue)
        {
            // Compute a new hash string.
            string expectedHashString = ComputeHashToBase64String(plainText, cryptoAlgorithm);

            // If the computed hash matches the specified hash,
            // the plain text value must be correct.
            return (hashedValue == expectedHashString);
        }

        /// <summary>
        /// Compares a hash of the specified plain text value to a given hash
        /// value.
        /// </summary>
        /// <param name="plainText">
        /// Plain text to be verified against the specified hash. The function
        /// does not check whether this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", 
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified,
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="hashedBytes">
        /// Hash value produced by ComputeHash function.
        /// </param>
        /// <returns>
        /// If computed hash mathes the specified hash the function the return
        /// value is true; otherwise, the function returns false.
        /// </returns>
        public static bool VerifyHash(string plainText, string cryptoAlgorithm, byte[] hashedBytes)
        {
            return VerifyHashInBase64String(plainText, GetCryptoAlgorithmByName(cryptoAlgorithm), Convert.ToBase64String(hashedBytes));
        }

        /// <summary>
        /// Compares a hash of the specified plain text value to a given hash
        /// value. Plain text is hashed with the same salt value as the original hash.
        /// </summary>
        /// <param name="plainText">
        /// Plain text to be verified against the specified hash. The function
        /// does not check whether this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", 
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified,
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="hashedBytes">
        /// Hash value produced by ComputeHash function.
        /// </param>
        /// <returns>
        /// If computed hash mathes the specified hash the function the return
        /// value is true; otherwise, the function returns false.
        /// </returns>
        public static bool VerifyHash(string plainText, CryptoAlgorithm cryptoAlgorithm, byte[] hashedBytes)
        {
            return VerifyHashInBase64String(plainText, cryptoAlgorithm, Convert.ToBase64String(hashedBytes));
        }

        #endregion

        #region CreateSalt/ComputeSaltedHash/VerifySaltedHash

        //The following generates and compares hashes using MD5, SHA1, SHA256, SHA384, 
        //and SHA512 hashing algorithms. Before computing a hash, it appends a
        //randomly generated salt to the plain text, and stores this salt appended
        //to the result. To verify another plain text value against the given hash,
        //this class will retrieve the salt value from the hash string and use it
        //when computing a new hash of the plain text. Appending a salt value to
        //the hash may not be the most efficient approach, so when using hashes in
        //a real-life application, you may choose to store them separately. You may
        //also opt to keep results as byte arrays instead of converting them into
        //base64-encoded strings.

        #region CreateSalt

        /// <summary>
        /// Creates salt for encryption by the specified salt size.
        /// </summary>
        /// <param name="saltSize">The salt size(must in range [1,32]).</param>
        /// <returns>The salt bytes.</returns>
        public static byte[] CreateSalt(int saltSize)
        {
            if (saltSize < 1 || 32 < saltSize)
                throw new ArgumentOutOfRangeException("The salt size must in range [1,32].");

            // Allocate a byte array, which will hold the salt.
            byte[] saltBytes = new byte[saltSize];

            // Initialize a random number generator.
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

            // Fill the salt with cryptographically strong byte values.
            rng.GetNonZeroBytes(saltBytes);

            return saltBytes;
        }

        /// <summary>
        /// Creates salt for encryption by the specified salt size 
        /// and returns hexadecimal result.
        /// </summary>
        /// <param name="saltSize">The salt size(must in range [1,32]).</param>
        /// <returns>The salt bytes formatted as a hexadecimal string.</returns>
        public static string CreateSaltToHexString(int saltSize)
        {
            return Utils.ByteArrayToHexString(CreateSalt(saltSize));
        }

        /// <summary>
        /// Creates salt for encryption by the specified salt size 
        /// and returns a base64-encoded result.
        /// </summary>
        /// <param name="saltSize">The salt size(must in range [1,32]).</param>
        /// <returns>The salt bytes formatted as a base64-encoded string.</returns>
        public static string CreateSaltToBase64String(int saltSize)
        {
            return Convert.ToBase64String(CreateSalt(saltSize));
        }

        /// <summary>
        /// Creates salt for encryption by the specified min salt size and max salt size.
        /// </summary>
        /// <param name="minSaltSize">The min salt size(must in range [1,32]).</param>
        /// <param name="maxSaltSize">The max salt size(must in range [1,32] and not less than min salt size).</param>
        /// <returns>The salt bytes.</returns>
        public static byte[] CreateSalt(int minSaltSize, int maxSaltSize)
        {
            // Define min and max salt sizes.
            int min = (minSaltSize >= 1 && 32 >= minSaltSize) ? minSaltSize : 1;
            int max = (maxSaltSize >= minSaltSize && 32 >= maxSaltSize) ? maxSaltSize : 32;

            // Generate a random number for the size of the salt.
            Random random = new Random();
            int saltSize = random.Next(min, max + 1);

            // Allocate a byte array, which will hold the salt.
            byte[] saltBytes = new byte[saltSize];

            // Initialize a random number generator.
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

            // Fill the salt with cryptographically strong byte values.
            rng.GetNonZeroBytes(saltBytes);

            return saltBytes;
        }

        /// <summary>
        /// Creates salt for encryption by the specified min salt size and max salt size 
        /// and returns hexadecimal result.
        /// </summary>
        /// <param name="minSaltSize">The min salt size(must in range [1,32]).</param>
        /// <param name="maxSaltSize">The max salt size(must in range [1,32] and not less than min salt size).</param>
        /// <returns>The salt bytes formatted as a hexadecimal string.</returns>
        public static string CreateSaltToHexString(int minSaltSize, int maxSaltSize)
        {
            return Utils.ByteArrayToHexString(CreateSalt(minSaltSize, maxSaltSize));
        }

        /// <summary>
        /// Creates salt for encryption by the specified min salt size and max salt size 
        /// and returns a base64-encoded result.
        /// </summary>
        /// <param name="minSaltSize">The min salt size(must in range [1,32]).</param>
        /// <param name="maxSaltSize">The max salt size(must in range [1,32] and not less than min salt size).</param>
        /// <returns>The salt bytes formatted as a base64-encoded string.</returns>
        public static string CreateSaltToBase64String(int minSaltSize, int maxSaltSize)
        {
            return Convert.ToBase64String(CreateSalt(minSaltSize, maxSaltSize));
        }

        #endregion

        #region ComputeSaltedHash

        /// <summary>
        /// Generates a hash for the given plain text value. 
        /// Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <returns>Hash value in bytes.</returns>
        public static byte[] ComputeSaltedHash(string plainText, string cryptoAlgorithm)
        {
            return ComputeSaltedHash(plainText, GetCryptoAlgorithmByName(cryptoAlgorithm), (byte[])null);
        }

        /// <summary>
        /// Generates a hash for the given plain text value. 
        /// Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <returns>Hash value in bytes.</returns>
        public static byte[] ComputeSaltedHash(string plainText, CryptoAlgorithm cryptoAlgorithm)
        {
            return ComputeSaltedHash(plainText, cryptoAlgorithm, (byte[])null);
        }

        /// <summary>
        /// Generates a hash for the given plain text value. 
        /// Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="saltText">
        /// Salt text. This parameter can be null, in which case a random salt
        /// value will be generated.
        /// </param>
        /// <returns>Hash value in bytes.</returns>
        public static byte[] ComputeSaltedHash(string plainText, string cryptoAlgorithm, string saltText)
        {
            return ComputeSaltedHash(plainText, GetCryptoAlgorithmByName(cryptoAlgorithm), Encoding.UTF8.GetBytes(saltText));
        }

        /// <summary>
        /// Generates a hash for the given plain text value. 
        /// Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="saltText">
        /// Salt text. This parameter can be null, in which case a random salt
        /// value will be generated.
        /// </param>
        /// <returns>Hash value in bytes.</returns>
        public static byte[] ComputeSaltedHash(string plainText, CryptoAlgorithm cryptoAlgorithm, string saltText)
        {
            return ComputeSaltedHash(plainText, cryptoAlgorithm, Encoding.UTF8.GetBytes(saltText));
        }

        /// <summary>
        /// Generates a hash for the given plain text value. 
        /// Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="saltBytes">
        /// Salt bytes. This parameter can be null, in which case a random salt
        /// value will be generated.
        /// </param>
        /// <returns>Hash value in bytes.</returns>
        public static byte[] ComputeSaltedHash(string plainText, string cryptoAlgorithm, byte[] saltBytes)
        {
            return ComputeSaltedHash(plainText, GetCryptoAlgorithmByName(cryptoAlgorithm), saltBytes);
        }

        /// <summary>
        /// Generates a hash for the given plain text value. 
        /// Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="saltBytes">
        /// Salt bytes. This parameter can be null, in which case a random salt
        /// value will be generated.
        /// </param>
        /// <returns>Hash value in bytes.</returns>
        public static byte[] ComputeSaltedHash(string plainText, CryptoAlgorithm cryptoAlgorithm, byte[] saltBytes)
        {
            // If salt is not specified, generate it on the fly.
            if (saltBytes == null || saltBytes.Length < 1)
                saltBytes = CreateSalt(4, 8);

            // Convert plain text into a byte array.
            byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);

            // Allocate array, which will hold plain text and salt.
            byte[] plainTextWithSaltBytes = new byte[plainTextBytes.Length + saltBytes.Length];

            // Copy plain text bytes into resulting array.
            for (int i = 0; i < plainTextBytes.Length; i++)
                plainTextWithSaltBytes[i] = plainTextBytes[i];

            // Append salt bytes to the resulting array.
            for (int i = 0; i < saltBytes.Length; i++)
                plainTextWithSaltBytes[plainTextBytes.Length + i] = saltBytes[i];

            // Because we support multiple hashing algorithms, we must define
            // hash object as a common (abstract) base class. We will specify the
            // actual hashing algorithm class later during object creation.
            HashAlgorithm hash;

            // Initialize appropriate hashing algorithm class.
            switch (cryptoAlgorithm)
            {
                case CryptoAlgorithm.SHA1:
                    hash = new SHA1Managed();
                    break;
                case CryptoAlgorithm.SHA256:
                    hash = new SHA256Managed();
                    break;
                case CryptoAlgorithm.SHA384:
                    hash = new SHA384Managed();
                    break;
                case CryptoAlgorithm.SHA512:
                    hash = new SHA512Managed();
                    break;
                default:
                    hash = new MD5CryptoServiceProvider();
                    break;
            }

            // Compute hash value of our plain text with appended salt.
            byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes);

            // Create array which will hold hash and original salt bytes.
            byte[] hashWithSaltBytes = new byte[hashBytes.Length + saltBytes.Length];

            // Copy hash bytes into resulting array.
            for (int i = 0; i < hashBytes.Length; i++)
                hashWithSaltBytes[i] = hashBytes[i];

            // Append salt bytes to the result.
            for (int i = 0; i < saltBytes.Length; i++)
                hashWithSaltBytes[hashBytes.Length + i] = saltBytes[i];

            // Return the result.
            return hashWithSaltBytes;
        }

        #endregion

        #region ComputeSaltedHashToHexString

        /// <summary>
        /// Generates a hash for the given plain text value and returns a
        /// hexadecimal result. Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <returns>Hash value formatted as a hexadecimal string.</returns>
        public static string ComputeSaltedHashToHexString(string plainText, string cryptoAlgorithm)
        {
            return ComputeSaltedHashToHexString(plainText, GetCryptoAlgorithmByName(cryptoAlgorithm), (byte[])null);
        }

        /// <summary>
        /// Generates a hash for the given plain text value and returns a
        /// hexadecimal result. Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <returns>Hash value formatted as a hexadecimal string.</returns>
        public static string ComputeSaltedHashToHexString(string plainText, CryptoAlgorithm cryptoAlgorithm)
        {
            return ComputeSaltedHashToHexString(plainText, cryptoAlgorithm, (byte[])null);
        }

        /// <summary>
        /// Generates a hash for the given plain text value and returns a
        /// hexadecimal result. Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="saltText">
        /// Salt text. This parameter can be null, in which case a random salt
        /// value will be generated.
        /// </param>
        /// <returns>Hash value formatted as a hexadecimal string.</returns>
        public static string ComputeSaltedHashToHexString(string plainText, string cryptoAlgorithm, string saltText)
        {
            return ComputeSaltedHashToHexString(plainText, GetCryptoAlgorithmByName(cryptoAlgorithm), Encoding.UTF8.GetBytes(saltText));
        }

        /// <summary>
        /// Generates a hash for the given plain text value and returns a
        /// hexadecimal result. Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="saltText">
        /// Salt text. This parameter can be null, in which case a random salt
        /// value will be generated.
        /// </param>
        /// <returns>Hash value formatted as a hexadecimal string.</returns>
        public static string ComputeSaltedHashToHexString(string plainText, CryptoAlgorithm cryptoAlgorithm, string saltText)
        {
            return ComputeSaltedHashToHexString(plainText, cryptoAlgorithm, Encoding.UTF8.GetBytes(saltText));
        }

        /// <summary>
        /// Generates a hash for the given plain text value and returns a
        /// hexadecimal result. Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="saltBytes">
        /// Salt bytes. This parameter can be null, in which case a random salt
        /// value will be generated.
        /// </param>
        /// <returns>Hash value formatted as a hexadecimal string.</returns>
        public static string ComputeSaltedHashToHexString(string plainText, string cryptoAlgorithm, byte[] saltBytes)
        {
            return ComputeSaltedHashToHexString(plainText, GetCryptoAlgorithmByName(cryptoAlgorithm), saltBytes);
        }

        /// <summary>
        /// Generates a hash for the given plain text value and returns a
        /// hexadecimal result. Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="saltBytes">
        /// Salt bytes. This parameter can be null, in which case a random salt
        /// value will be generated.
        /// </param>
        /// <returns>Hash value formatted as a hexadecimal string.</returns>
        public static string ComputeSaltedHashToHexString(string plainText, CryptoAlgorithm cryptoAlgorithm, byte[] saltBytes)
        {
            // Convert result into a hexadecimal string.
            return Utils.ByteArrayToHexString(ComputeSaltedHash(plainText, cryptoAlgorithm, saltBytes));
        }

        #endregion

        #region ComputeSaltedHashToBase64String

        /// <summary>
        /// Generates a hash for the given plain text value and returns a
        /// base64-encoded result. Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <returns>Hash value formatted as a base64-encoded string.</returns>
        public static string ComputeSaltedHashToBase64String(string plainText, string cryptoAlgorithm)
        {
            return ComputeSaltedHashToBase64String(plainText, GetCryptoAlgorithmByName(cryptoAlgorithm), (byte[])null);
        }

        /// <summary>
        /// Generates a hash for the given plain text value and returns a
        /// base64-encoded result. Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <returns>Hash value formatted as a base64-encoded string.</returns>
        public static string ComputeSaltedHashToBase64String(string plainText, CryptoAlgorithm cryptoAlgorithm)
        {
            return ComputeSaltedHashToBase64String(plainText, cryptoAlgorithm, (byte[])null);
        }

        /// <summary>
        /// Generates a hash for the given plain text value and returns a
        /// base64-encoded result. Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="saltText">
        /// Salt text. This parameter can be null, in which case a random salt
        /// value will be generated.
        /// </param>
        /// <returns>Hash value formatted as a base64-encoded string.</returns>
        public static string ComputeSaltedHashToBase64String(string plainText, string cryptoAlgorithm, string saltText)
        {
            return ComputeSaltedHashToBase64String(plainText, GetCryptoAlgorithmByName(cryptoAlgorithm), Encoding.UTF8.GetBytes(saltText));
        }

        /// <summary>
        /// Generates a hash for the given plain text value and returns a
        /// base64-encoded result. Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="saltText">
        /// Salt text. This parameter can be null, in which case a random salt
        /// value will be generated.
        /// </param>
        /// <returns>Hash value formatted as a base64-encoded string.</returns>
        public static string ComputeSaltedHashToBase64String(string plainText, CryptoAlgorithm cryptoAlgorithm, string saltText)
        {
            return ComputeSaltedHashToBase64String(plainText, cryptoAlgorithm, Encoding.UTF8.GetBytes(saltText));
        }

        /// <summary>
        /// Generates a hash for the given plain text value and returns a
        /// base64-encoded result. Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="saltBytes">
        /// Salt bytes. This parameter can be null, in which case a random salt
        /// value will be generated.
        /// </param>
        /// <returns>Hash value formatted as a base64-encoded string.</returns>
        public static string ComputeSaltedHashToBase64String(string plainText, string cryptoAlgorithm, byte[] saltBytes)
        {
            return ComputeSaltedHashToBase64String(plainText, GetCryptoAlgorithmByName(cryptoAlgorithm), saltBytes);
        }

        /// <summary>
        /// Generates a hash for the given plain text value and returns a
        /// base64-encoded result. Before the hash is computed, a random salt
        /// is generated and appended to the plain text. This salt is stored at
        /// the end of the hash value, so it can be used later for hash
        /// verification.
        /// </summary>
        /// <param name="plainText">
        /// Plaintext value to be hashed. The function does not check whether
        /// this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1",
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="saltBytes">
        /// Salt bytes. This parameter can be null, in which case a random salt
        /// value will be generated.
        /// </param>
        /// <returns>Hash value formatted as a base64-encoded string.</returns>
        public static string ComputeSaltedHashToBase64String(string plainText, CryptoAlgorithm cryptoAlgorithm, byte[] saltBytes)
        {
            // Convert result into a base64-encoded string.
            return Convert.ToBase64String(ComputeSaltedHash(plainText, cryptoAlgorithm, saltBytes));
        }

        #endregion

        #region VerifySaltedHash(InHexString/InBase64String)

        /// <summary>
        /// Compares a hash of the specified plain text value to a given hash
        /// value. Plain text is hashed with the same salt value as the original hash.
        /// </summary>
        /// <param name="plainText">
        /// Plain text to be verified against the specified hash. The function
        /// does not check whether this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", 
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified,
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="hashedHexValue">
        /// Hash value produced by ComputeSaltedHashToHexString function. This value
        /// includes the original salt appended to it.
        /// </param>
        /// <returns>
        /// If computed hash mathes the specified hash the function the return
        /// value is true; otherwise, the function returns false.
        /// </returns>
        public static bool VerifySaltedHashInHexString(string plainText, string cryptoAlgorithm, string hashedHexValue)
        {
            return VerifySaltedHashInHexString(plainText, GetCryptoAlgorithmByName(cryptoAlgorithm), hashedHexValue);
        }


        /// <summary>
        /// Compares a hash of the specified plain text value to a given hash
        /// value. Plain text is hashed with the same salt value as the original hash.
        /// </summary>
        /// <param name="plainText">
        /// Plain text to be verified against the specified hash. The function
        /// does not check whether this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", 
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified,
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="hashedHexValue">
        /// Hash value produced by ComputeSaltedHashToHexString function. This value
        /// includes the original salt appended to it.
        /// </param>
        /// <returns>
        /// If computed hash mathes the specified hash the function the return
        /// value is true; otherwise, the function returns false.
        /// </returns>
        public static bool VerifySaltedHashInHexString(string plainText, CryptoAlgorithm cryptoAlgorithm, string hashedHexValue)
        {
            // Convert hexadecimal hash value into a byte array.
            byte[] hashWithSaltBytes = Utils.HexStringToByteArray(hashedHexValue);

            return VerifySaltedHash(plainText, cryptoAlgorithm, hashWithSaltBytes);
        }

        /// <summary>
        /// Compares a hash of the specified plain text value to a given hash
        /// value. Plain text is hashed with the same salt value as the original hash.
        /// </summary>
        /// <param name="plainText">
        /// Plain text to be verified against the specified hash. The function
        /// does not check whether this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", 
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified,
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="hashedBase64Value">
        /// Hash value produced by ComputeSaltedHashToBase64String function. This value
        /// includes the original salt appended to it.
        /// </param>
        /// <returns>
        /// If computed hash mathes the specified hash the function the return
        /// value is true; otherwise, the function returns false.
        /// </returns>
        public static bool VerifySaltedHashInBase64String(string plainText, string cryptoAlgorithm, string hashedBase64Value)
        {
            return VerifySaltedHashInBase64String(plainText, GetCryptoAlgorithmByName(cryptoAlgorithm), hashedBase64Value);
        }


        /// <summary>
        /// Compares a hash of the specified plain text value to a given hash
        /// value. Plain text is hashed with the same salt value as the original hash.
        /// </summary>
        /// <param name="plainText">
        /// Plain text to be verified against the specified hash. The function
        /// does not check whether this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", 
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified,
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="hashedBase64Value">
        /// Hash value produced by ComputeSaltedHashToBase64String function. This value
        /// includes the original salt appended to it.
        /// </param>
        /// <returns>
        /// If computed hash mathes the specified hash the function the return
        /// value is true; otherwise, the function returns false.
        /// </returns>
        public static bool VerifySaltedHashInBase64String(string plainText, CryptoAlgorithm cryptoAlgorithm, string hashedBase64Value)
        {
            // Convert base64-encoded hash value into a byte array.
            byte[] hashWithSaltBytes = Convert.FromBase64String(hashedBase64Value);

            return VerifySaltedHash(plainText, cryptoAlgorithm, hashWithSaltBytes);
        }


        /// <summary>
        /// Compares a hash of the specified plain text value to a given hash
        /// value. Plain text is hashed with the same salt value as the original hash.
        /// </summary>
        /// <param name="plainText">
        /// Plain text to be verified against the specified hash. The function
        /// does not check whether this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", 
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified,
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="hashedBytes">
        /// Hash value produced by ComputeSaltedHash function. This value
        /// includes the original salt appended to it.
        /// </param>
        /// <returns>
        /// If computed hash mathes the specified hash the function the return
        /// value is true; otherwise, the function returns false.
        /// </returns>
        public static bool VerifySaltedHash(string plainText, string cryptoAlgorithm, byte[] hashedBytes)
        {
            return VerifySaltedHash(plainText, GetCryptoAlgorithmByName(cryptoAlgorithm), hashedBytes);
        }

        /// <summary>
        /// Compares a hash of the specified plain text value to a given hash
        /// value. Plain text is hashed with the same salt value as the original hash.
        /// </summary>
        /// <param name="plainText">
        /// Plain text to be verified against the specified hash. The function
        /// does not check whether this parameter is null.
        /// </param>
        /// <param name="cryptoAlgorithm">
        /// Name of the hash algorithm. Allowed values are: "MD5", "SHA1", 
        /// "SHA256", "SHA384", and "SHA512" (if any other value is specified,
        /// MD5 hashing algorithm will be used). This value is case-insensitive.
        /// </param>
        /// <param name="hashedBytes">
        /// Hash value produced by ComputeSaltedHash function. This value
        /// includes the original salt appended to it.
        /// </param>
        /// <returns>
        /// If computed hash mathes the specified hash the function the return
        /// value is true; otherwise, the function returns false.
        /// </returns>
        public static bool VerifySaltedHash(string plainText, CryptoAlgorithm cryptoAlgorithm, byte[] hashedBytes)
        {
            byte[] hashWithSaltBytes = hashedBytes;

            // We must know size of hash (without salt).
            int hashSizeInBits, hashSizeInBytes;

            // Size of hash is based on the specified algorithm.
            switch (cryptoAlgorithm)
            {
                case CryptoAlgorithm.SHA1:
                    hashSizeInBits = 160;
                    break;
                case CryptoAlgorithm.SHA256:
                    hashSizeInBits = 256;
                    break;
                case CryptoAlgorithm.SHA384:
                    hashSizeInBits = 384;
                    break;
                case CryptoAlgorithm.SHA512:
                    hashSizeInBits = 512;
                    break;
                default: // Must be MD5
                    hashSizeInBits = 128;
                    break;
            }

            // Convert size of hash from bits to bytes.
            hashSizeInBytes = hashSizeInBits / 8;

            // Make sure that the specified hash value is long enough.
            if (hashWithSaltBytes.Length < hashSizeInBytes)
                return false;

            // Allocate array to hold original salt bytes retrieved from hash.
            byte[] saltBytes = new byte[hashWithSaltBytes.Length - hashSizeInBytes];

            // Copy salt from the end of the hash to the new array.
            for (int i = 0; i < saltBytes.Length; i++)
                saltBytes[i] = hashWithSaltBytes[hashSizeInBytes + i];

            // Compute a new hash string.
            string expectedHashString = ComputeSaltedHashToBase64String(plainText, cryptoAlgorithm, saltBytes);

            // If the computed hash matches the specified hash,
            // the plain text value must be correct.
            return (Convert.ToBase64String(hashedBytes) == expectedHashString);
        }

        #endregion

        #endregion

        #endregion



        /// <summary>
        /// This class provides MD5 cryptography services.
        /// This class cannot be inherited.
        /// </summary>
        public static class MD5
        {
            /// <summary>
            /// Computes the MD5 hash value for the specified string to hexadecimal string using Encoding.UTF8.
            /// </summary>
            /// <param name="input">The input to compute the hash code for.</param>
            /// <returns>Hash value formatted as a hexadecimal string.</returns>
            public static string EncryptToHexString(string input)
            {
                return EncryptToHexString(Encoding.UTF8.GetBytes(input));
            }

            /// <summary>
            /// Computes the MD5 hash value for the specified byte array to hexadecimal string.
            /// </summary>
            /// <param name="buffer">The input to compute the hash code for.</param>
            /// <returns>Hash value formatted as a hexadecimal string.</returns>
            public static string EncryptToHexString(byte[] buffer)
            {
                var md5 = new StringBuilder(32);
                foreach (byte b in Encrypt(buffer))
                {
                    md5.AppendFormat("{0:x2}", b);
                }

                return md5.ToString().ToUpper();
            }

            /// <summary>
            /// Computes the MD5 hash value for the specified System.IO.Stream object to hexadecimal string.
            /// </summary>
            /// <param name="inputStream">The input to compute the hash code for.</param>
            /// <returns>Hash value formatted as a hexadecimal string.</returns>
            public static string EncryptToHexString(Stream inputStream)
            {
                var md5 = new StringBuilder(32);
                foreach (byte b in Encrypt(inputStream))
                {
                    md5.AppendFormat("{0:x2}", b);
                }

                return md5.ToString().ToUpper();
            }

            /// <summary>
            /// Computes the MD5 hash value for the specified string to base64 string using Encoding.UTF8.
            /// </summary>
            /// <param name="input">The input to compute the hash code for.</param>
            /// <returns>Hash value formatted as a base64-encoded string.</returns>
            public static string EncryptToBase64String(string input)
            {
                return EncryptToBase64String(Encoding.UTF8.GetBytes(input));
            }

            /// <summary>
            /// Computes the MD5 hash value for the specified byte array to base64 string.
            /// </summary>
            /// <param name="buffer">The input to compute the hash code for.</param>
            /// <returns>Hash value formatted as a base64-encoded string.</returns>
            public static string EncryptToBase64String(byte[] buffer)
            {
                return Convert.ToBase64String(Encrypt(buffer));
            }


            /// <summary>
            /// Computes the MD5 hash value for the specified System.IO.Stream object to base64 string.
            /// </summary>
            /// <param name="inputStream">The input to compute the hash code for.</param>
            /// <returns>Hash value formatted as a base64-encoded string.</returns>
            public static string EncryptToBase64String(Stream inputStream)
            {
                return Convert.ToBase64String(Encrypt(inputStream));
            }


            /// <summary>
            /// Computes the MD5 hash value for the specified string using Encoding.UTF8.
            /// </summary>
            /// <param name="input">The input to compute the hash code for.</param>
            /// <returns>The computed hash code.</returns>
            public static byte[] Encrypt(string input)
            {
                MD5CryptoServiceProvider md5Provider = new MD5CryptoServiceProvider();
                return md5Provider.ComputeHash(Encoding.UTF8.GetBytes(input));
            }

            /// <summary>
            /// Computes the MD5 hash value for the specified byte array.
            /// </summary>
            /// <param name="buffer">The input to compute the hash code for.</param>
            /// <returns>The computed hash code.</returns>
            public static byte[] Encrypt(byte[] buffer)
            {
                MD5CryptoServiceProvider md5Provider = new MD5CryptoServiceProvider();
                return md5Provider.ComputeHash(buffer);
            }

            /// <summary>
            /// Computes the hash value for the specified region of the specified byte array.
            /// </summary>
            /// <param name="buffer">The input to compute the hash code for.</param>
            /// <param name="offset">The offset into the byte array from which to begin using data.</param>
            /// <param name="count">The number of bytes in the array to use as data.</param>
            /// <returns>The computed hash code.</returns>
            public static byte[] Encrypt(byte[] buffer, int offset, int count)
            {
                MD5CryptoServiceProvider md5Provider = new MD5CryptoServiceProvider();
                return md5Provider.ComputeHash(buffer, offset, count);
            }

            /// <summary>
            /// Computes the hash value for the specified System.IO.Stream object.
            /// </summary>
            /// <param name="inputStream">The input to compute the hash code for.</param>
            /// <returns>The computed hash code.</returns>
            public static byte[] Encrypt(Stream inputStream)
            {
                MD5CryptoServiceProvider md5Provider = new MD5CryptoServiceProvider();
                return md5Provider.ComputeHash(inputStream);
            }


            /// <summary>
            /// Compares a hash of the specified plain text value to a given hash
            /// value.
            /// </summary>
            /// <param name="inputString">
            /// Plain text to be verified against the specified hash. The function
            /// does not check whether this parameter is null.
            /// </param>
            /// <param name="hashedString">Hash value produced by EncryptToHexString function.</param>
            /// <returns>
            /// If computed hash mathes the specified hash the function the return
            /// value is true; otherwise, the function returns false.
            /// </returns>
            public static bool VerifyInHexString(string inputString, string hashedString)
            {
                return (EncryptToHexString(inputString) == hashedString);
            }

            /// <summary>
            /// Compares a hash of the specified plain text value to a given hash
            /// value.
            /// </summary>
            /// <param name="inputString">
            /// Plain text to be verified against the specified hash. The function
            /// does not check whether this parameter is null.
            /// </param>
            /// <param name="hashedString">Hash value produced by EncryptToBase64String function.</param>
            /// <returns>
            /// If computed hash mathes the specified hash the function the return
            /// value is true; otherwise, the function returns false.
            /// </returns>
            public static bool VerifyInBase64String(string inputString, string hashedString)
            {
                return (EncryptToBase64String(inputString) == hashedString);
            }
        }
    }
}
